package controllers

import (
	"context"
	"encoding/json"
	"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/logs"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers/lease"
	"gitverse.ru/synapse/kubelatte/pkg/util"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"k8s.io/apimachinery/pkg/api/errors"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
	"strings"
	"sync"
)

// TriggerReconciler reconciles a Template object
type TriggerReconciler struct {
	client.Client
	Scheme *runtime.Scheme
	once   sync.Once

	triggerObj        v1alpha1.Trigger
	deletedTriggerObj v1alpha1.Trigger

	initedFlag bool
}

const (
	errorTriggerPrefix           = "Trigger error: "
	infoTriggersOperationPostfix = " operation for a custom \"kind: Trigger\" configuration"
)

func (r *TriggerReconciler) deferLog(req ctrl.Request, configData []byte, err error) {
	var status v1alpha1.LogStatus
	var inf string
	var level zapcore.Level

	if err == nil {
		inf = string(eventOperation) + infoTriggersOperationPostfix
		status = v1alpha1.SuccessStatus
		level = 1
	} else {
		inf = errorTriggerPrefix + err.Error()
		status = v1alpha1.FailedStatus
		level = zap.ErrorLevel
	}
	logs.PersistLog(level, "rqUID", getRqUID(clusterName, req.Namespace, triggerType, req.Name), "message", string(configData),
		"information", inf, "status", status, "operationName", eventOperation, "serviceReceiver", serviceReceiver)
}

func (r *TriggerReconciler) AddOrUpdateEventHndl(ctx context.Context, req ctrl.Request) (err error) {
	confByte, _ := json.Marshal(r.triggerObj.Spec)

	r.deferLog(req, confByte, err)

	return nil
}

func (r *TriggerReconciler) DeleteEventHndl(req ctrl.Request) (err error) {

	confByte, _ := json.Marshal(r.deletedTriggerObj.Spec)
	r.deferLog(req, confByte, err)
	return nil
}

func (r *TriggerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	Mux.Lock()
	defer Mux.Unlock()

	r.once.Do(func() {
		r.initedFlag = true
	})
	// Check delete event
	err := r.Get(ctx, req.NamespacedName, &r.triggerObj)
	if err != nil {
		if errors.IsNotFound(err) {
			if err = r.DeleteEventHndl(req); err != nil {
				return ctrl.Result{}, err
			}
		}
		return ctrl.Result{}, err
	}

	// Add or update event
	err = r.AddOrUpdateEventHndl(ctx, req)
	if err != nil {
		return ctrl.Result{}, err
	}

	return r.updateStatus(ctx, r.triggerObj, false)
}

func (r *TriggerReconciler) updateStatus(ctx context.Context, trigger v1alpha1.Trigger, isAudit bool) (ctrl.Result, error) {
	if lease.AmILeader() {
		data, _ := json.Marshal(trigger.Spec)
		if CheckHashInStatus(trigger.Status.Conditions) {
			for i, condition := range trigger.Status.Conditions {
				if condition.Type == v1alpha1.LastHandledHash {
					sha1Hash := GetHash(data)
					if isAudit && strings.Compare(trigger.Status.Conditions[i].Message, sha1Hash) != 0 {
						logs.Debugf("Trigger status checker: version compare failed: %s != %s", trigger.Status.Conditions[i].Message, sha1Hash)
					}
					trigger.Status.Conditions[i].Message = sha1Hash
					trigger.Status.Conditions[i].LastTransitionTime = v1.Now()
				}
			}
		} else {
			trigger.Status.Conditions = append(trigger.Status.Conditions, AddHashCondition(data))
		}
		err := r.Status().Update(ctx, &trigger, &client.SubResourceUpdateOptions{})
		if err != nil {
			logs.Error("Trigger controller: failed update status crd: " + err.Error())
			return ctrl.Result{}, err
		}
	}
	return ctrl.Result{}, nil
}

func (r *TriggerReconciler) manageEvents() predicate.Predicate {
	return predicate.Funcs{
		CreateFunc: func(e event.CreateEvent) bool {
			eventOperation = addEvent
			obj := *e.Object.(*v1alpha1.Trigger)
			if IsOperatorInClusterMode() && !CheckNamespace(obj.Namespace, NSSelectorCommon) {
				return false
			}
			util.SendEvent(StorageUpdateTriggerEvent, obj)
			pmTriggersTotal.Inc()
			return true
		},
		UpdateFunc: func(e event.UpdateEvent) bool {
			if e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() {
				eventOperation = updateEvent
				obj := *e.ObjectNew.(*v1alpha1.Trigger)
				if IsOperatorInClusterMode() && !CheckNamespace(obj.Namespace, NSSelectorCommon) {
					return false
				}
				util.SendEvent(StorageUpdateTriggerEvent, obj)
				return true
			}
			return false
		},
		DeleteFunc: func(e event.DeleteEvent) bool {
			eventOperation = deleteEvent
			r.deletedTriggerObj = *e.Object.(*v1alpha1.Trigger)
			if IsOperatorInClusterMode() && !CheckNamespace(r.deletedTriggerObj.Namespace, NSSelectorCommon) {
				return false
			}
			util.SendEvent(StorageDeleteTriggerEvent, r.deletedTriggerObj)
			pmTriggersTotal.Dec()
			return true
		},
	}
}

// SetupWithManager sets up the controller with the Manager.
func (r *TriggerReconciler) SetupWithManager(mgr ctrl.Manager) error {

	return ctrl.NewControllerManagedBy(mgr).
		For(&v1alpha1.Trigger{}).
		WithEventFilter(r.manageEvents()).
		Complete(r)
}

func (r *TriggerReconciler) HealthCheck() bool {
	return r.initedFlag
}
